[S3] S3を挟んでS3に一覧ログを記録してみました
1 はじめに
CX事業本部の平内(SIN)です。
「処理ごとに蓄積される一覧(ログ)を、リアルタイムにS3上に作成する」というニーズを叶えようと、昨日、一昨日と下記のような試みをしましたが、どちらもイマイチでした。
結局、「一旦DynamoDBとかに入れてから処理する」という結論になるのですが・・・
今回は、S3をDB代わりに使用して「一旦S3にログを出力して、定期的に、一覧を生成する」というのを試してみました。
こちらは、S3の「結果整合性」と戦うことはありませんので、もしよかったら、読み進めて頂ければ幸いです。
注意:すいません、DynamoDBを使用するよりメリットが有る!とかでは無いです。
2 処理+ログ記録
「処理+ログ記録」側の実装は、下記のようなものです。
import * as AWS from 'aws-sdk'; exports.handler = async (event: any) => { await log(event.message); } const prefix = 'Target'; // 識別子(的なもの) const bucketName = `log-sample-temporary`; async function log(message: string) { const dt = new Date(); const s3 = new AWS.S3(); const body = { message: `${createDateTime(dt)} ${message}` } const params = { Bucket: bucketName, Key: createKeyName(dt), Body: JSON.stringify(body) }; return await s3.putObject(params).promise(); } function createKeyName(dt: Date){ const Y = dt.getFullYear(); const M = ("00" + (dt.getMonth()+1)).slice(-2); const D = ("00" + dt.getDate()).slice(-2); const h = ("00" + (dt.getHours())).slice(-2); const m = ("00" + (dt.getMinutes())).slice(-2); const s = ("00" + (dt.getSeconds())).slice(-2); const ms = ("000" + (dt.getMilliseconds())).slice(-3); const rand = Math.random().toString(10).slice(-10); return `${prefix}/${Y}-${M}-${D}-${h}${m}${s}-${ms}-${rand}`; } function createDateTime(dt: Date){ const Y = dt.getFullYear(); const M = ("00" + (dt.getMonth()+1)).slice(-2); const D = ("00" + dt.getDate()).slice(-2); const h = ("00" + (dt.getHours())).slice(-2); const m = ("00" + (dt.getMinutes())).slice(-2); const s = ("00" + (dt.getSeconds())).slice(-2); const ms = ("000" + (dt.getMilliseconds())).slice(-3); return `${Y}/${M}/${D} ${h}:${m}:${s}.${ms}`; }
上のコードでは、prefixをつけて、階層下にログファイルを生成しています。
3 集計処理
集計側では、最初に、prefixsの一覧を作成して、prefixsごとに、その中のログを列挙して、prefixs.logという名前の一覧を生成しています。
import * as AWS from 'aws-sdk'; const bucketName = `log-sample-temporary`; exports.handler = async (event: any) => { console.log(JSON.stringify(event)); // prefixs一覧の作成 const prefixs = await getPrefixs(); // 一覧の作成 const s3 = new AWS.S3(); await Promise.all(prefixs.map( async prefix => { const log = await getLog(prefix); console.log(log); await s3.putObject({Bucket: bucketName, Key: `${prefix}.log`, Body: log} ).promise(); })); } async function getPrefixs(): Promise<string[]> { const s3 = new AWS.S3(); let result:string[] = []; const params = { Bucket: bucketName, }; const list = await s3.listObjects(params).promise(); if(list && list.Contents){ await Promise.all(list.Contents.map( async content => { const key = content.Key!; const tmp = key.split('/'); if(tmp.length >= 2) { if(result.indexOf(tmp[0])==-1){ result.push(tmp[0]) } } })); } return result; } async function getLog(prefix: string): Promise<string> { const s3 = new AWS.S3(); let result = ''; let keys: string[] = []; const params = { Prefix: prefix, Bucket: bucketName, }; const list = await s3.listObjects(params).promise(); if(list && list.Contents){ list.Contents.forEach(content=>{ const key = content.Key!; if(key != `${prefix}.log`){ keys.push(key); } }) } keys = keys.sort(); await Promise.all(keys.map( async key => { const data = await s3.getObject( {Bucket: bucketName, Key: key} ).promise(); const json = JSON.parse(data.Body!.toString("utf-8")); result += `${json.message}\n`; })); return result; }
生成された一覧です。
2019/11/04 15:14:31.819 1 2019/11/04 15:14:31.939 5 2019/11/04 15:14:31.981 3 2019/11/04 15:14:32.206 6 2019/11/04 15:14:32.274 2 2019/11/04 15:11:57.156 75 2019/11/04 15:11:57.172 71 2019/11/04 15:14:32.396 0 2019/11/04 15:14:32.603 7 2019/11/04 15:14:32.640 14 2019/11/04 15:14:32.788 8 2019/11/04 15:14:32.940 10 2019/11/04 15:14:33.040 12 2019/11/04 15:14:33.076 16 2019/11/04 15:14:33.500 15 2019/11/04 15:14:33.501 17 ・・・略
4 トリガー
当初、S3へのPutをトリガーにして、試してみたのですが、集計処理が高速で起動すると処理しきれないので、CloudWatch Eventsにしました。この処理は、ログの量に応じて、時間がかかるので、スケジュールの間隔は、それに応じて決定する必要があります。
同時実行は、並列動作しても、ほとんど意味ないので1でいいでしょう。
5 最後に
今回は、ログをS3に出力して、定期的にそれを集計(一覧生成)するのを試してみました。 不要になった一次ログは、ライフサイクルとかで自動的に削除してしまうことにします。